﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;

namespace Wedding.Provider
{
    public abstract class AbsContext<T> where T : System.Data.Linq.DataContext
    {
        private object _cnnOld;
        private object _cnn;
        private T _dataContext;
        private T DataContext
        {
            get
            {
                if (_dataContext == null)
                {
                    InstanceContext();
                }
                return _dataContext;
            }
        }

        private bool InstanceContext()
        {
            try
            {
                if (_dataContext != null && DbTransaction == null)
                {
                    _dataContext.Dispose();
                    _dataContext = null;
                }
                if (_dataContext == null)
                {
                    Type type = typeof(T);
                    _dataContext = type.InvokeMember(type.Name, System.Reflection.BindingFlags.CreateInstance, null, null, new object[] { _cnn }) as T;
                    if (DbTransaction != null) _dataContext.Transaction = DbTransaction;
                }
                return true;
            }
            catch
            {
                return false;
            }
        }
        private void ProceedError(Exception e)
        {
            InstanceContext();
            throw e;
        }

        private void CopyToGen(object entity, object obj)
        {
            if (obj == null) return;

            var properties = entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
            var typeObj = obj.GetType();
            foreach (var property in properties)
            {
                if (property.Name == "EntityKey"
                    || property.Name == "EntityState"
                    || property.Name == "CreatedUser"
                    || property.Name == "CreatedDate"
                    || property.Name == "UpdatedUser"
                    || property.Name == "UpdatedDate"
                    || property.Name == "DeletedUser"
                    || property.Name == "DeletedDate"
                    || property.Name == "IsDeleted") continue;
                var propertyObj = typeObj.GetProperty(property.Name);
                if (propertyObj != null && property.PropertyType == propertyObj.PropertyType && propertyObj.CanWrite)
                {
                    propertyObj.SetValue(obj, property.GetValue(entity, null), null);
                }
            }
        }

        protected DbTransaction DbTransaction { get; private set; }

        public void OpenTransaction(DbTransaction dbTransaction = null)
        {
            if (DbTransaction != null) throw new Exception("Exist a transaction in current context");

            if (dbTransaction == null)
            {
                DataContext.Connection.Open();
                DbTransaction = DataContext.Connection.BeginTransaction();
                DataContext.Transaction = DbTransaction;
                _cnnOld = _cnn;
                _cnn = DbTransaction.Connection;
            }
            else
            {
                _cnnOld = _cnn;
                _cnn = dbTransaction.Connection;
                DataContext.Transaction = dbTransaction;
            }
        }

        public bool CommitTransaction()
        {
            if (DataContext == null) return false;
            DbTransaction dbTran = DataContext.Transaction;
            if (dbTran == null) return false;
            var conn = dbTran.Connection;
            if (conn != null && conn.State == ConnectionState.Open)
            {
                try
                {
                    dbTran.Commit();
                }
                catch (Exception ex)
                {
                    dbTran.Rollback();
                    ProceedError(ex);
                }
                finally
                {
                    conn.Close();
                    DbTransaction = null;
                    _cnn = _cnnOld;
                }
            }
            return true;
        }

        public void RollbackTransaction()
        {
            if (DataContext == null) return;
            DbTransaction dbTran = DataContext.Transaction;
            if (dbTran == null) return;
            var conn = dbTran.Connection;
            if (conn != null && conn.State == ConnectionState.Open)
            {
                dbTran.Rollback();
                conn.Close();
                DbTransaction = null;
                _cnn = _cnnOld;
            }
        }

        public AbsContext(string connectionString)
        {
            _cnn = connectionString;
        }
        public AbsContext(DbConnection dbConnection)
        {
            _cnn = dbConnection;
        }

        public IQueryable<TEntity> GetQuery<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate = null) where TEntity : class
        {
            InstanceContext();
            if (predicate == null) return DataContext.GetTable<TEntity>();
            return DataContext.GetTable<TEntity>().Where(predicate);
        }

        public TEntity GetObject<TEntity>(Func<TEntity, bool> predicate) where TEntity : class
        {
            TEntity result = null;
            try
            {
                InstanceContext();
                result = DataContext.GetTable<TEntity>().FirstOrDefault<TEntity>(predicate);
            }
            catch (Exception e)
            {
                ProceedError(e);
            }
            return result;
        }

        public bool AddObject<TEntity>(TEntity obj) where TEntity : class
        {
            if (obj == null) return false;
            var result = false;
            Type typeEntity = obj.GetType();
            try
            {
                InstanceContext();
                DataContext.GetTable<TEntity>().InsertOnSubmit(obj);
                DataContext.SubmitChanges();
                result = true;
            }
            catch (Exception e)
            {
                ProceedError(e);
            }
            return result;
        }

        public bool UpdateObject<TEntity>(Func<TEntity, bool> predicate, TEntity obj) where TEntity : class
        {
            if (obj == null) return false;
            var result = false;
            Type typeEntity = obj.GetType();
            try
            {
                InstanceContext();
                var item = GetObject(predicate);// DataContext.GetObject(predicate, strUserName);
                CopyToGen(obj, item);
                DataContext.SubmitChanges();
                result = true;
            }
            catch (Exception e)
            {
                ProceedError(e);
            }
            return result;
        }

        public bool DeleteObject<TEntity>(Func<TEntity, bool> predicate) where TEntity : class
        {
            var result = false;
            try
            {
                InstanceContext();
                var item = GetObject(predicate);
                DataContext.GetTable<TEntity>().DeleteOnSubmit(item);
                DataContext.SubmitChanges();
                result = true;
            }
            catch (Exception e)
            {
                ProceedError(e);
                return false;
            }
            return result;
        }
    }
}
